package com.soup.lab.third.token;

import com.alibaba.fastjson.JSONObject;
import com.soup.lab.third.lock.KeyLock;
import com.soup.lab.third.lock.RedisKeyLock;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.JedisCluster;

import java.util.function.Function;

/**
 * 加入redis缓存的 token获取器包装类
 */
public class RedisTokenFetcher implements TokenFetcher {

    private static final Logger logger = LoggerFactory.getLogger(RedisTokenFetcher.class);

    private final JedisCluster jedisCluster;

    private final KeyLock keyLock;

    private final TokenFetcher tokenFetcher;

    private final Function<TokenConfig, String> redisKeyGetter;
    /**
     * 对于独占token，同时只允许一个进程更新token。需要使用分布式锁保护。
     */
    public static final String GUARD_LOCK_SUFFIX = ":lock";

    public RedisTokenFetcher(JedisCluster jedisCluster, KeyLock keyLock, TokenFetcher tokenFetcher, Function<TokenConfig, String> redisKeyGetter) {
        this.jedisCluster = jedisCluster;
        this.keyLock = keyLock;
        this.tokenFetcher = tokenFetcher;
        this.redisKeyGetter = redisKeyGetter;
    }

    public RedisTokenFetcher(JedisCluster jedisCluster, TokenFetcher tokenFetcher, Function<TokenConfig, String> redisKeyGetter) {
        this(jedisCluster, new RedisKeyLock(jedisCluster), tokenFetcher, redisKeyGetter);
    }

    @Override
    public Token fetchToken(TokenConfig tokenConfig, boolean forceUpdate) {
        if(!tokenConfig.getTokenProperty().isEnabled()) {
            return null;
        }
        String redisKey = redisKeyGetter.apply(tokenConfig);
        // 处理独占token
        if(tokenConfig.getTokenProperty().isExclusive()) {
            String guardRedisKey = redisKey + GUARD_LOCK_SUFFIX;
            keyLock.lock(guardRedisKey);
            try {
                return getOrUpdateToken(tokenConfig, redisKey, forceUpdate);
            } finally {
                keyLock.unlock(guardRedisKey);
            }
        }
        // 非独占token,允许多个进程同时更新token,不用抢占分布式锁
        return getOrUpdateToken(tokenConfig, redisKey, forceUpdate);
    }

    public Token getOrUpdateToken(TokenConfig tokenConfig, String redisKey, boolean forceUpdate) {
        if(forceUpdate) {
            return updateToken(tokenConfig, redisKey, true);
        }
        String tokenStr = jedisCluster.get(redisKey);
        logger.debug("读取redis缓存中的token，redisKey:{}, tokenStr:{}", redisKey, tokenStr);
        if (StringUtils.isBlank(tokenStr)) {
            return updateToken(tokenConfig, redisKey, forceUpdate);
        }
        Token token = null;
        try {
            token = JSONObject.parseObject(tokenStr, Token.class);
        } catch (Exception e) {
            logger.error("读取redis缓存中的token失败，redisKey:{}, tokenStr:{}", redisKey, tokenStr, e);
        }
        if (token == null || !token.isValid()) {
            return updateToken(tokenConfig, redisKey, false);
        }
        return token;
    }

    private Token updateToken(TokenConfig tokenConfig, String redisKey, boolean forceUpdate) {
        Token token = tokenFetcher.fetchToken(tokenConfig, forceUpdate);
        int expire;
        if(token != null && token.isValid() && (expire = token.getExpireSeconds()) > 0) {
            String val = JSONObject.toJSONString(token);
            logger.debug("向redis缓存中写入token，redisKey:{}, tokenStr:{}", redisKey, val);
            if("ok".equalsIgnoreCase(jedisCluster.setex(redisKey, expire, val))) {
                logger.debug("向redis缓存中写入token成功，redisKey:{}, tokenStr:{}", redisKey, val);
                return token;
            }
            logger.debug("向redis缓存中写入token失败，redisKey:{}, tokenStr:{}", redisKey, val);
        }
        return token;
    }

}
